home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / info-service / www / dev / mail2html / mail2HTML.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-01-31  |  31.7 KB  |  1,037 lines

  1. /************************************************************************
  2.  *                                                                      *
  3.  *  Program:                    mail2HTML.c                             *
  4.  *                                                                      *
  5.  *----------------------------------------------------------------------*
  6.  *  Description:  Convert Mail/News Files to HTML (Prototype)           *
  7.  *                                                                      *
  8.  *----------------------------------------------------------------------*
  9.  *  Copyright (C) 1993  Basis Systeme netzwerk (BSn)                    *
  10.  *                      Franz-Wolter Strasse 42                         *
  11.  *                      D-8000 Munich 81                                *
  12.  *                      Federal Republic of Germany                     *
  13.  *                                                                      *
  14.  *    Redistribution and use in source and binary forms are permitted   *
  15.  *    provided that the above copyright notice and this paragraph are   *
  16.  *    duplicated in  all such forms  and that  any documentation,       *
  17.  *    advertising materials,  and other materials related to such       *
  18.  *    distribution and use acknowledge that the software was developed  *
  19.  *    by Basis Systeme netzwerk/Munich.                                 *
  20.  *                                                                      *
  21.  *    This is distributed in the hope that it will be useful, but       *
  22.  *    WITHOUT ANY WARRANTY;  without even the implied warranty of       *
  23.  *    MERCHANTABILITY  or  FITNESS FOR A PARTICULAR PURPOSE.            *
  24.  *                                                                      *
  25.  ************************************************************************/
  26.  
  27. #ifndef lint
  28. static char     RCS_id[] = "$Header: /usr/export/home/edz/WWW/experimental/RCS/mail2html.c,v 0.02 1993/01/21 20:36:49 edz Exp edz $";
  29.  
  30. #endif
  31. /*************
  32.  * $Log: mail2html.c,v $
  33.  * Revision 0.3  1993/01/21  20:36:49  edz
  34.  * Prepare for distribution
  35.  *
  36.  * Revision 0.2  1993/01/21  08:10:49  edz
  37.  * Removed Redundant address from Table of Contents structure
  38.  * Changed the name of a few functions
  39.  *
  40.  * Revision 0.1  1993/01/10  20:25:49  edz
  41.  * Initial revision
  42.  *
  43.  *************/
  44. #define _MAIN_C
  45. /************************************************************************
  46.  * WARNING:                                                             *
  47.  *    DON'T complain that this looks like a "one-off" hack! Why?        *
  48.  *    'cause it is.                                                     *
  49.  *    I don't seem to have any mail around here that breaks this        *
  50.  *    program and it seems to work (and I hope follows RFC822) but      *
  51.  *    I am sure that SOMEONE must have some mail that this very simple  *
  52.  *    parser breaks (Greetings from Murphy).                            *
  53.  *                                                                      *
  54.  * NOTES:                                                               *
  55.  *    You need an ANSI-C compiler and libraries to compile this.        *
  56.  *                                                                      *
  57.  *    (Q) Why did I not use Perl?                                       *
  58.  *    (A) I have not figured out why EVERYONE else in using Perl        *
  59.  *        for these kinds of programs.                                  *
  60.  *    (Q) Why did I write this?                                         *
  61.  *    (A) Mail like Usenet News is a very common information source     *
  62.  *        that is already Hypertext (The file system, man pages and     *
  63.  *        your Window systems are also Hypertext).                      *
  64.  *        Before you even have a chance to ask. Hypertext is a MODEL    *
  65.  *        of Representation not an implementation. The World Wide Web,  *
  66.  *        SOFABED, HyTime, Xerox Notes, HyperCard, HyTelnet ... are     *
  67.  *        all implementations of a model for hypertext. The programs    *
  68.  *        www, viola, hytelnet, notes, ... are all user interfaces to   *
  69.  *        the respective implementations.                               *
  70.  *                                                                      *
  71.  *        The often cited reasons for the success of the Gopher model   *
  72.  *        in contrast to the W3 model: that W3 requires hypertext       *
  73.  *        documents and that hypertext documents are rare is faulty.    *
  74.  *        A html editor would be welcome but is not as important as     *
  75.  *        the community belief has stipulated.                          *
  76.  *        W3 represents a paradigm change.                              *
  77.  *                                                                      *
  78.  *        With  Man pages, Mail, News, native HTML, GNU Info,           *
  79.  *        Hytelnet/MaxThink, Internet Resource files (FAQs),            *
  80.  *        WAIS, Gopher (the current gateway is incomplete),             *
  81.  *        Archie, Directory Assistance (DIXIE/X500), CSO phone          *
  82.  *        books                                                         *
  83.  *        ... there is more than enough!                                *
  84.  *                                                                      *
  85.  *        With some simple and transparents auto-tagers the whole       *
  86.  *        Internet, Usenet and ... is the web. We hope to have a man    *
  87.  *        page autotagger done in the near future (the current crop     *
  88.  *        --- at least the ones I know--- are NOT autotaggers).         *
  89.  *                                                                      *
  90.  *    This program is only a technology test. We hope to have a         *
  91.  *    a mail transport done is the very near future.                    *
  92.  *    We are currently experimenting with several user authenitication  *
  93.  *    schemes (a'la POP).                                               *
  94.  *                                                                      *
  95.  *    If you use this TEST-code we would like to hear from you.         *
  96.  *                                                                      *
  97.  * TODO:                                                                *
  98.  *    (i)   Add Support for MIME (RFC1341)                              *
  99.  *    (ii)  Add Support for Configuration (RRC1343)                     *
  100.  *    (ii)  Add Support for Multinational headers (RFC1342)             *
  101.  *    (iii) Add X.400 Support                                           *
  102.  *    (iv)  Use a more intellegent message ID algorithm. The current    *
  103.  *          implementation requires that the folder that contains the   *
  104.  *          reference was at one time accessed, viz. that a delivery    *
  105.  *          or cron process ran a bogus parse. Given the other info     *
  106.  *          available (eg. time) one could narrow the search.           *
  107.  *    (v)   Use ndbm/gdbm instead of stupid ASCII file list/Remove      *
  108.  *          ID Duplication.                                             *
  109.  *    (vi)  Fold into WWW Daemon (see Gopher), eg.                      *
  110.  *          WWW/Mail/mbox  returns the table of Contents                *
  111.  *          references have names like "/MessageID/AA04187@BSNGATE"     *
  112.  *    (vii) Fold in state system transport (currently its stateless)    *
  113.  ************************************************************************/
  114. /*-
  115.  *  /MessageID/<MAIL-ID> returns the .html file corresponding to <MAIL-ID>
  116.  *
  117.  */
  118. #include <stdlib.h>
  119. #include <stdio.h>
  120. #include <string.h>
  121. #include <sys/stat.h>
  122. #include "Entities.h"
  123. #include "Ctypes.h"
  124. #include "Ctypes.c"
  125.  
  126. #ifndef MAXPATHLEN
  127. # ifdef _MAX_PATH
  128. #  define MAXPATHLEN _MAX_PATH
  129. # else
  130. #  define MAXPATHLEN 1024
  131. # endif
  132. #endif
  133.  
  134. /*----------------------- User Configuration ----------------------------- */
  135. /* The file below must be readable and writeable */
  136. #define MESSAGE_INDEX_FILE  "/var/adm/MESSAGES.INDEX"
  137. #define CONTENTS_EXTENSION  ".MAIL_BODIES"
  138. #define DIRECTORY_EXTENSION ".TABLE_OF_CONTENTS"
  139.  
  140. /*---------------------- End User Configuration -------------------------- */
  141.  
  142. #ifndef TRUE
  143. # define TRUE  1
  144. # define FALSE 0
  145. #endif
  146.  
  147. /* Exit Codes */
  148. #define E_ENOENT    02, "Sorry Document is not available or access is restricted"
  149. #define E_USAGE     64, "Incorrect Document request"
  150. #define E_NOINPUT   66, "Cannot open input"
  151. #define E_IOERR     74    "input/output error"
  152. #define E_SOFTWARE  70, "internal software error"
  153. #define E_NOTEMP    75, "INTERNAL ERROR: Can't create a file!"
  154.  
  155. static const char IndexFile[] = MESSAGE_INDEX_FILE;
  156. static const char body_ext[] = CONTENTS_EXTENSION;
  157. static const char HEX[] = "0123456789ABCDEF";
  158.  
  159. #ifdef _MSC_VER            /* The Microsoft compiler (Xenix/OS2/NT) */
  160. # define strncasecomp strnicmp
  161. #else
  162.  
  163. /* Case INDEPENDENT version of strncmp() */
  164. static int
  165. strncasecomp(const char *str1, const char *str2, size_t n)
  166. {
  167.     const char     *p = str1;
  168.     const char     *q = str2;
  169.     int             diff;
  170.  
  171.     for (p = str1, q = str2;; p++, q++) {
  172.         if (p == str1 + n)
  173.             return 0;
  174.         if (*p == '\0' || *q == '\0')
  175.             return *p - *q;
  176.         if ((diff = tolower(*p) - tolower(*q)) != 0)
  177.             return diff;
  178.     }
  179.     /* NOTREACHED */
  180. }
  181.  
  182. #endif
  183.  
  184. #ifdef NEED_STRDUP
  185. static char    *
  186. strdup(const char *str)
  187. {
  188.     char           *tcp;
  189.  
  190.     if ((tcp = (char *) malloc(strlen(str) + 1)) != NULL)
  191.         strcpy(tcp, str);
  192.     return tcp;
  193. }
  194. #endif
  195.  
  196.  
  197. /* Structure to Build a table of contents */
  198. typedef struct _contents {
  199.     char           *anchor;    /* Reference (HREF="<anchor>")   */
  200.     char           *subject;/* Name of anchor                */
  201.     char           *author;    /* Full Name of Author           */
  202.     int             isNews;    /* 1 ==> News else Mail          */
  203.     char           *group;    /* Newgroup (NULL if Mail)       */
  204.     long            start;    /* start of message (offset)     */
  205.     struct _contents *next;    /* Next element in linked list   */
  206. }               contents_t;
  207.  
  208. /* Add an anchor to the table of contents */
  209. void
  210. AddMessage(contents_t ** Contents, long start, int isNews, const char *anchor,
  211.        const char *subject, const char *author, const char *group)
  212. {
  213.     contents_t     *tp;
  214.  
  215.     /* Build a table of contents in reverse order */
  216.     if ((tp = (contents_t *) malloc(sizeof(contents_t))) != NULL) {
  217.         tp -> start = start;
  218.         tp -> isNews = isNews;
  219.         tp -> anchor = strdup(anchor);
  220.         tp -> subject = strdup(subject);
  221.         tp -> author = strdup(author);
  222.         tp -> group = (isNews ? strdup(group) : NULL);
  223.         tp -> next = *Contents;
  224.         *Contents = tp;
  225.     }
  226. }
  227.  
  228. /*
  229.  * This is a quick hack to speed up searching the MESSAGES.INDEX file
  230.  * for the correct entry
  231.  */
  232. char
  233. HASH(const unsigned char *name)
  234. {
  235.     unsigned short  hash = 0;
  236.     int             i;
  237.  
  238.     for (i = 0; name[i]; i++)
  239.         hash += (((short)TOISO(name[i])) << (1 + (i % 8))) + name[i];
  240.     return (char) ((hash % 225) + 30);
  241. }
  242.  
  243. /* Encode reference as HTML compliant */
  244. static char    *
  245. EncodeAnchor(char *buf, const unsigned char *anchor, int case_sensitive)
  246. {
  247.     char           *tp1 = buf;
  248.     const char     *tp2;
  249.     unsigned char   ch;
  250.  
  251.     /* Note RFC822 specifies 7-bit headers (Message IDs are 6 bit) */
  252.     /* Replace non acceptable chars (# and %) and make uppercase */
  253.     for (tp2 = anchor; (ch = *tp2) != '\0'; tp2++)
  254.         if (!ISPATH(ch) || ch == '#' || ch == '%') {
  255.             *tp1++ = '%';
  256.             *tp1++ = HEX[(TOISO(ch) & '\377') >> 4];
  257.             *tp1++ = HEX[(TOISO(ch) & '\377') % 16];
  258.         } else
  259.             *tp1++ = (case_sensitive ? (char)ch : (char)TOUPPER(ch));
  260.     *tp1 = '\0';
  261.     return (buf);
  262. }
  263.  
  264. /* Append dictionary to parsed message id list */
  265. void
  266. DumpDictionary(contents_t * Contents, const char *filename, long end)
  267. {
  268.     if (Contents) {
  269.         contents_t     *tp;
  270.         FILE           *fp;
  271.         char            path[MAXPATHLEN + 256];
  272.  
  273.         if ((fp = fopen(IndexFile, "a")) == NULL)
  274.             return;
  275.  
  276.         EncodeAnchor(path, filename, TRUE);
  277.         for (tp = Contents; tp != NULL; tp = tp -> next) {
  278.             fprintf(fp, "%c%s\t%s%s\t%ld-%ld\tFrom %s: %s\n",
  279.                 HASH(tp -> anchor),
  280.                 tp -> anchor,
  281.                 path, body_ext,
  282.                 tp -> start, end,
  283.                 tp -> author, tp -> subject);
  284.             end = tp -> start;
  285.         }
  286.     }
  287. }
  288.  
  289.  
  290. /* Print the Table of Contents */
  291. void
  292. PrintContents(contents_t * Contents, FILE * fp)
  293. {
  294.     if (Contents != NULL) {
  295.         contents_t     *tp;
  296.  
  297.         fprintf(fp, "<!-- Table of Contents for this file (reverse order) -->\n");
  298.         fprintf(fp, "<H1>Table of Contents</H1>\n<DL>\n");
  299.         for (tp = Contents; tp != NULL; tp = tp -> next) {
  300.             fprintf(fp, "<DT><A HREF=\"/MessageID/%s\">", tp -> anchor);
  301.             if (tp -> isNews)
  302.                 fprintf(fp, "%s in %s", tp -> author, tp -> group);
  303.             else
  304.                 fprintf(fp, "%s from %s", "Mail", tp -> author);
  305.             fprintf(fp, "</A><DD>%s\n", tp -> subject);
  306.         }
  307.         fprintf(fp, "</DL><P>\n\n");
  308.     }
  309. }
  310.  
  311. /* Strip trailing white space */
  312. char           *
  313. StripTail(char *line)
  314. {
  315.     char           *tcp = line + strlen(line) - 1;
  316.  
  317.     while (*tcp == '\r' || *tcp == '\n' || *tcp == ' ' || *tcp == '\t')
  318.         *tcp-- = '\0';
  319.     return line;
  320. }
  321.  
  322. /* Strip trailing white space and move to first non-white character */
  323. static char    *
  324. HTStrip(char *line)
  325. {
  326.     char           *tcp;
  327.  
  328.     for (tcp = StripTail(line); ISWHITE(*tcp); tcp++)
  329.          /* loop */ ;
  330.     return tcp;
  331. }
  332.  
  333.  
  334. /* Rewind input and copy to output stream */
  335. static void
  336. CatStream(FILE * infp, FILE * outfp)
  337. {
  338.     register int    ch;
  339.  
  340.     if (infp) {
  341.         fflush(infp);
  342.         rewind(infp);
  343.         while ((ch = getc(infp)) != EOF)
  344.             putc((char) ch, outfp);
  345.     }
  346. }
  347.  
  348. /* Decode HTML reference */
  349. static char    *
  350. DecodeAnchor(char *buf, const unsigned char *anchor)
  351. {
  352.     char           *tp1 = buf;
  353.     const char     *tp2 = anchor;
  354.  
  355.     while (*tp2) {
  356.         if (*tp2 == '%') {
  357.             char           *tcp;
  358.             unsigned        ch = 0;
  359.  
  360.             if ((tcp = strchr(HEX, *++tp2)) != NULL)
  361.                 ch = (tcp - HEX) << 4;
  362.             if ((tcp = strchr(HEX, *++tp2)) != NULL)
  363.                 ch += tcp - HEX;
  364.             *tp1 = (char)ch;
  365.             tp2++;
  366.         } else
  367.             *tp1++ = *tp2++;
  368.     }
  369.     *tp1 = '\0';
  370.     return buf;
  371. }
  372.  
  373.  
  374. /* In "XXX <YYY> ZZZZ" return "YYY" */
  375. static char    *
  376. MessageKey(char *buf, char *line)
  377. {
  378.     char           *tp1;
  379.     char           *tp2;
  380.  
  381.     if ((tp1 = strchr(line, '<')) != NULL) {
  382.         if ((tp2 = strchr(++tp1, '>')) != NULL)
  383.             *tp2 = '\0';
  384.     } else
  385.         tp1 = line;
  386.     /* Message Keys are CASE INSENSITIVE */
  387.     return EncodeAnchor(buf, HTStrip(tp1), FALSE);
  388. }
  389.  
  390. /*-
  391.  * Find Author's name in mail address
  392.  * In "XXX (YYY)" or YYY <XXX>" return "YYY"
  393.  * Find Author's address in mail address
  394.  * In "XXX (YYY)" or YYY <XXX>" return "XXX"
  395.  */
  396. static char    *
  397. NameKey(char *buf, const char *key, int author)
  398. {
  399.     char           *s, *e;
  400.     char            email[256];
  401.     char            p1, p2, b1, b2;
  402.  
  403.     if (author) {
  404.         p1 = '('; p2 = ')';
  405.         b1 = '<'; b2 = '>';
  406.     } else {
  407.         p1 = '<'; p2 = '>';
  408.         b1 = '('; b2 = ')';
  409.     }
  410.  
  411.     strcpy(email, key);
  412.     if (((s = strchr(email, p1)) != NULL) && ((e = strchr(email, p2)) != NULL)) {
  413.         if (e > s) {
  414.             *e = '\0';    /* Chop off everything after p2 (')' or '>') */
  415.             strcpy(email, s + 1);
  416.         }
  417.     } else if (((s = strchr(email, b1)) != NULL) && ((e = strchr(email, b2)) != NULL)) {
  418.         if (e > s)
  419.             strcpy(s, e + 1);    /* Remove <...> or (...) */
  420.     }
  421.     strcpy(buf, HTStrip(email));    /* Remove leading and trailing spaces */
  422.     return buf;
  423. }
  424.  
  425.  
  426.  
  427. /*
  428.  * This Function returns a static storage area, it is the duty of the caller
  429.  * to save it.
  430.  */
  431. static char    *
  432. Anchor(char *line)
  433. {
  434.     char           *tp1;
  435.     char           *tp2 = line;
  436.     static char     tmp[BUFSIZ];
  437.  
  438.     tmp[0] = '\0';
  439.     while (tp2 != NULL && (tp1 = strchr(tp2, '<')) != NULL) {
  440.         if (tp1 > tp2) {
  441.             *tp1 = '\0';
  442.             strcat(tmp, tp2);
  443.         }
  444.         if ((tp2 = strchr(++tp1, '>')) != NULL)
  445.             *tp2++ = '\0';
  446.         tp1 = HTStrip(tp1);
  447.         strcat(tmp, "<A HREF=\"/MessageID/");
  448.         EncodeAnchor(tmp + strlen(tmp), tp1, FALSE);
  449.         sprintf(tmp + strlen(tmp), "\">%s</A>", tp1);
  450.     }            /* while */
  451.     if (tp2 != NULL)
  452.         strcat(tmp, tp2);
  453.     return tmp;
  454. }
  455.  
  456. /*-
  457.  * Send out a line of the message body.
  458.  * (1) Use Latin-1 public entities
  459.  * (2) Translate URLs, eg. ftp://site:port/path to:
  460.  *       <A HREF="ftp/site:port/path">ftp/site:port/path</A>
  461.  * (3) Markup VT100 Style underlined text (eg. man) as
  462.  *     Strong.
  463.  * We don't confirm that the protocol is valid (registered)
  464.  * --- would be very simple but the list is growing too fast
  465.  * (file, ftp, http, wais, gopher, prospero, ... )
  466.  */
  467. static char *
  468. BodyLine(char *outbuf, unsigned char *line, int nl)
  469. {
  470.     unsigned char  *tcp = outbuf;
  471.     unsigned char  *tp = line;
  472.     unsigned char   ch;
  473.  
  474.     while (( ch = *tp) != '\0')
  475.         if (ch == '_' && *(tp+1) == '\b') {
  476.             char            buf[256];
  477.             char            tmp[256];
  478.             char           *ptr = buf;
  479.  
  480.             /* VT100 Underlined text */
  481.             do {
  482.                 tp += 2; /* Skip _<Ctrl-H> */
  483.                 *ptr++ = *tp++;
  484.             } while (*tp == '_' && *(tp+1) == '\b');
  485.             *ptr = '\0';
  486.  
  487.             ptr = BodyLine(tmp, buf, FALSE); /* Fixup chars */
  488.             /* Underlined Text is marked strong */
  489.             strcpy(tcp, "<strong>");
  490.             strcat(tcp, ptr);
  491.             strcat(tcp, "</strong>");
  492.             tcp += strlen(tcp);
  493.  
  494.         } else if ((ch == ':') && (*(tp+1) == '/') && (*(tp+2) == '/') &&
  495.                (tp >= &line[2]) && ISALPHA(*(tp-1))) {
  496.             unsigned char  *tp2;
  497.  
  498.             /* Saw a URL Magic Back up */
  499.             do {
  500.                 --tp; --tcp;
  501.             } while (ISALPHA(*tp) && tp >= line);
  502.             if (!ISALPHA(*tp)) {
  503.                 tp++; tcp++;
  504.             }
  505.  
  506.             /* Insert Anchor */
  507.             for (tp2 = tp; ISPATH(*tp2); tp2++)
  508.                  /* loop */ ;
  509.             if (( *(tcp-1) == '"' || *(tcp-1) == '\'') && *tp2 == *(tp-1)) {
  510.                 /* quoted arguments */
  511.                 *tp2++ = '\0';    /* ASCIIZ */
  512.                 sprintf(--tcp, "<A HREF=\"%s\">%s%c</A>", tp, tp - 1, *(tp-1));
  513.             } else {
  514.                 char            ch;
  515.  
  516.                 ch = *tp2;    /* Save character */
  517.                 *tp2 = '\0';    /* ASCIIZ */
  518.                 sprintf(tcp, "<A HREF=\"%s\">%s</A>", tp, tp);
  519.                 *tp2 = ch;    /* Replace character */
  520.             }
  521.             tp = tp2;    /* Set pointer to tail */
  522.             tcp += strlen(tcp);    /* Go to tail */
  523.         } else if (Markups[ch].len) {
  524.             memcpy(tcp, Markups[ch].entity, Markups[ch].len);
  525.             tcp += Markups[ch].len;
  526.             tp++;
  527.         } else if (!ISASCII(ch) || (ISCNTRL(ch) && !ISWHITE(ch))) {
  528.             *tcp++ = '&';
  529.             *tcp++ = '#';
  530.             *tcp++ = (unsigned char) ((TOISO(ch) / 100) + '0');
  531.             *tcp++ = (unsigned char) ((TOISO(ch) % 100) / 10 + '0');
  532.             *tcp++ = (unsigned char) ((TOISO(ch) % 10) + '0');
  533.             *tcp++ = ';';
  534.             tp++;
  535.         } else
  536.             *tcp++ = *tp++;
  537.     if (nl) *tcp++ = '\n';
  538.     *tcp = '\0';
  539.     return outbuf;
  540. }
  541.  
  542. /* Put a line of the Body */
  543. static void
  544. PutBodyLine(unsigned char *line, FILE * outfp)
  545. {
  546.     unsigned char   outbuf[BUFSIZ];
  547.  
  548.     fputs(BodyLine(outbuf, line, TRUE), outfp);
  549. }
  550.  
  551. /* Headers should be 7 bit */
  552. static char *
  553. HeaderLine(char *outbuf, unsigned char *line)
  554. {
  555.     /* For now pretend we have 8 bit headers (latter RFC1342) */
  556.     return BodyLine(outbuf, line, FALSE);
  557. }
  558.  
  559. /*-
  560.  * IsMailFromLine - Is this a legal unix mail "From " line?
  561.  *
  562.  * Given a line of input will check to see if it matches the standard
  563.  * unix mail "from " header format. Returns 0 if it does and <0 if not.
  564.  *
  565.  * 2 - Very strict, also checks that each field contains a legal value.
  566.  *
  567.  * Assumptions: Not having the definitive unix mailbox reference I have
  568.  * assumed that unix mailbox headers follow this format:
  569.  *
  570.  * From <person> <date> <garbage>
  571.  *
  572.  * Where <person> is the address of the sender, being an ordinary
  573.  * string with no white space imbedded in it, and <date> is the date of
  574.  * posting, in ctime(3C) format.
  575.  *
  576.  * This would, on the face of it, seem valid. I (Bernd) have yet to find a
  577.  * unix mailbox header which doesn't follow this format.
  578.  *
  579.  * From: Bernd Wechner (bernd@bhpcpd.kembla.oz.au)
  580.  * Obfuscated by: KFS (as usual)
  581.  */
  582.  
  583. static int
  584. IsMailFromLine(char *line)
  585. {
  586. #define MAX_FIELDS 10
  587.     char           *fields[MAX_FIELDS];
  588.     char           *sender_tail;
  589.     register char  *lp, **fp;
  590.     register int    n, i;
  591.     const char      legal_day[] = "SunMonTueWedThuFriSat";
  592.     const char      legal_month[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
  593.     const int       legal_numbers[] = {1, 31, 0, 23, 0, 59, 0, 60, 1969, 2199};
  594.  
  595.     if (strncmp(line, "From ", 5))   return -100;
  596.  
  597.     lp = line + 5;
  598.     /* sender day mon dd hh:mm:ss year */
  599.     for (n = 0, fp = fields; n < MAX_FIELDS; n++) {
  600.         while (*lp && *lp != '\n' && ISASCII(*lp) && ISWHITE(*lp))  lp++;
  601.         if (*lp == '\0' || *lp == '\n')  break;
  602.         *fp++ = lp;
  603.         while (*lp && ISASCII(*lp) && !ISWHITE(*lp))
  604.             if (*lp++ == ':' && (n == 4 || n == 5))   break;
  605.         if (n == 0) sender_tail = lp;
  606.     }
  607.  
  608.     if (n < 8)  return -200 - n;
  609.  
  610.     fp = fields;
  611.  
  612.     if (n > 8 && !ISNUM(fp[7][0]))   fp[7] = fp[8];    /* ... TZ year */
  613.     if (n > 9 && !ISNUM(fp[7][0]))   fp[7] = fp[9];    /* ... TZ DST year */
  614.  
  615.     fp++;
  616.     for (i = 0; i < 21; i += 3)
  617.         if (strncmp(*fp, &legal_day[i], 3) == 0)  break;
  618.     if (i == 21)   return -1;
  619.  
  620.     fp++;
  621.     for (i = 0; i < 36; i += 3)
  622.         if (strncmp(*fp, &legal_month[i], 3) == 0)
  623.             break;
  624.     if (i == 36)   return -2;
  625.  
  626.     for (i = 0; i < 10; i += 2) {
  627.         lp = *++fp;
  628.         if (!ISNUM(*lp))  return -20 - i;
  629.         n = atoi(lp);
  630.         if (n < legal_numbers[i] || legal_numbers[i + 1] < n) return -10 - i;
  631.     }
  632.     return 0;
  633. }
  634.  
  635. /*-
  636.  * Start of News:
  637.  * "Article <Number> of <Newsgroup>:"
  638.  */
  639. static int
  640. IsNewsLine(char *line, int *article, char **group)
  641. {
  642.     int             i;
  643.  
  644.     if (strncmp(line, "Article ", 8)) return -500;
  645.     line += 8;
  646.     /* Skip white space */
  647.     while (ISWHITE(*line))  line++;
  648.  
  649.     if (!ISNUM(*line))    return -400;
  650.     i = atoi(line);
  651.     /* skip number data */
  652.     while (ISNUM(*line))  line++;
  653.  
  654.     if (!ISWHITE(*line))    return -300;
  655.     /* Skip white space */
  656.     while (ISWHITE(*line))  line++;
  657.  
  658.     if (line[0] != 'o' || line[1] != 'f')  return -200;
  659.     /* Skip the of */
  660.     line += 2;
  661.  
  662.     if (!ISWHITE(*line))    return -100;
  663.     /* Skip white space */
  664.     while (ISWHITE(*line))  line++;
  665.  
  666.     if (*line == '\0') return -10;    /* Missing Group */
  667.  
  668.     /* OK, if was "Article NNN of XXX.XXX.XXXXX:" */
  669.     {
  670.         static char     grp[60];
  671.         char            tmp[126];
  672.         size_t          tail;
  673.  
  674.         strncpy(grp, HeaderLine(tmp, line), sizeof(grp) - 1);
  675.         grp[sizeof(grp)] = '\0';
  676.         /* Strip trailing ':' if it has one */
  677.         if (grp[tail = strlen(grp) - 1] == ':')
  678.             grp[tail] = '\0';
  679.         if (group)     *group = grp;
  680.         if (article)   *article = i;
  681.     }
  682.     return 0;
  683. }
  684.  
  685. /* Structure to store the header information (envelope) */
  686. typedef struct {
  687.     char            cc[128];            /* cc:                  */
  688.     char            bcc[128];           /* bcc:                 */
  689.     char            from[64];           /* Reply-To: or From:   */
  690.     char            address[64];
  691.     char            subject[128];       /* Subject:             */
  692.     char            date[40];           /* Date:                */
  693.     char            id[80];             /* Message-ID:          */
  694.     char            keywords[256];      /* Keywords:            */
  695.     char            organization[80];   /* Organization:        */
  696.     char            followup[126];      /* Followup-To:         */
  697.     char            newsgroups[BUFSIZ]; /* Newgroups:           */
  698.     char            xrefs[BUFSIZ / 2];    /* In-Reply-To:         */
  699.     char            refs[BUFSIZ];       /* References:          */
  700. }               envelope_t;
  701.  
  702. static char    *
  703. ReadHeaderLine(char *buf, size_t len, FILE *infp)
  704. {
  705.     char           *tcp;
  706.     int             ahead; /* lookahead token */
  707.  
  708.     if ((tcp = fgets(buf, len, infp)) != NULL) {
  709.         /* Check if continuation line */
  710.         while ((ahead = fgetc(infp)) == '\t' || ahead == ' ') {
  711.             char            tmp[256];
  712.  
  713.             if (fgets(tmp, sizeof(tmp), infp) != NULL) {
  714.                 tcp = StripTail(buf);
  715.                 strcat(tcp, " ");
  716.                 strcat(tcp, tmp);
  717.             }
  718.         }        /* while */
  719.         ungetc(ahead, infp);        /* push back */
  720.     }
  721.     return StripTail(tcp);
  722. }
  723.  
  724. static int
  725. ParseRFC822Header(envelope_t * envelope, FILE * infp)
  726. {
  727.     char            tmp[BUFSIZ];
  728.     char           *tcp;
  729.     int             mime = 0;
  730.  
  731.     memset(envelope, 0, sizeof(envelope_t));
  732.  
  733.     /* Read the header bits */
  734.     /* Everything after first null line is message body */
  735.     while ((tcp = ReadHeaderLine(tmp, sizeof(tmp), infp)) != NULL && *tcp) {
  736.         /* TAGS in RFC-822 Header */
  737.         switch (*tcp++) {
  738.             case 'b': case 'B':    /* possible bcc: */
  739.                 if (strncasecomp("cc: ", tcp, 4) == 0)
  740.                     HeaderLine(envelope -> bcc, tcp + 4);
  741.                 break;
  742.             case 'c': case 'C':    /* possible cc: or Content-<*>: */
  743.                 /* MIME NOT YET SUPPORTED */
  744.                 if (strncasecomp("ontent-", tcp, 7) == 0)
  745.                     mime++;
  746.                 else if (strncasecomp("c: ", tcp, 3) == 0)
  747.                     HeaderLine(envelope -> cc, tcp + 3);
  748.                 break;
  749.             case 'd': case 'D':    /* possible Date: */
  750.                 if (strncasecomp("ate: ", tcp, 5) == 0)
  751.                     HeaderLine(envelope -> date, tcp + 5);
  752.                 break;
  753.             case 'f': case 'F':    /* possible From: or Followup-To: */
  754.                 /* "Reply-to" SUPERSEDES the "From" field */
  755.                 if (*(envelope -> from) == '\0' && strncasecomp("rom: ", tcp, 5) == 0) {
  756.                     NameKey(envelope -> address, tcp + 5, FALSE);
  757.                     NameKey(envelope -> from, tcp + 5, TRUE);
  758.                 } else if (strncasecomp("ollowup-To: ", tcp, 12) == 0)
  759.                     strcpy(envelope -> followup, tcp + 12);
  760.                 break;
  761.             case 'i': case 'I':    /* possible In-Reply-To: */
  762.                 if (strncasecomp("n-Reply-To: ", tcp, 12) == 0)
  763.                     strcpy(envelope -> xrefs, tcp + 12);
  764.                 break;
  765.             case 'k': case 'K':    /* possible Keywords: */
  766.                 if (strncasecomp("eywords: ", tcp, 9) == 0)
  767.                     HeaderLine(envelope -> keywords, tcp + 9);
  768.                 break;
  769.             case 'm': case 'M':    /* possible Message-ID: or MIME-Version: */
  770.                 if (strncasecomp("essage-ID: ", tcp, 11) == 0)
  771.                     MessageKey(envelope -> id, tcp + 11);
  772.                 else if (strncasecomp("IME-Version: ", tcp, 13) == 0)
  773.                     mime++;
  774.                 break;
  775.             case 'n': case 'N':    /* possible Newsgroups: */
  776.                 if (strncasecomp("ewsgroups: ", tcp, 11) == 0)
  777.                     HeaderLine(envelope -> newsgroups, tcp + 11);
  778.                 break;
  779.             case 'o': case 'O':    /* possible Organization: */
  780.                 if (strncasecomp("rganization: ", tcp, 13) == 0)
  781.                     HeaderLine(envelope -> organization, tcp + 13);
  782.                 break;
  783.             case 'r': case 'R':    /* possible Reply-To: or References: */
  784.                 if (strncasecomp("eply-To: ", tcp, 9) == 0) {
  785.                     NameKey(envelope -> address, tcp + 9, FALSE);
  786.                     NameKey(envelope -> from, tcp + 9, TRUE);
  787.                 } else if (strncasecomp("eferences: ", tcp, 11) == 0)
  788.                     strcpy(envelope -> refs, tcp + 11);
  789.                 break;
  790.             case 's': case 'S':    /* possible Subject: or Sender: */
  791.                 if (strncasecomp("ubject: ", tcp, 8) == 0)
  792.                     HeaderLine(envelope -> subject, tcp + 8);
  793.                 break;
  794.         }        /* switch */
  795.     }
  796.  
  797.     if (*(envelope -> id) == '\0') {
  798.         static int      count = 1;
  799.  
  800.         /* generate psuedo ID */
  801.         sprintf(envelope -> id, "FAKE%05o", count++);
  802.     }
  803.     if (*(envelope -> subject) == '\0')
  804.         strcpy(envelope -> subject, "No Subject");
  805.     return mime;
  806. }
  807.  
  808. static void
  809. LocateAnchors(contents_t ** Contents, int isNews, char *group, FILE * infp, FILE * outfp)
  810. {
  811.     int             mime;
  812.     envelope_t      envelope;
  813.     static const char item[] = "<DT>%s:<DD>%s\n";
  814.  
  815. #define xItem(s, n, v)  if (s) fprintf(outfp, item, n, v);
  816. #define Item(n, v)      xItem(*(v), (n), (v))
  817.  
  818.     mime = ParseRFC822Header(&envelope, infp);
  819.  
  820.     /* Add Message To Table of Contents */
  821.     AddMessage(Contents
  822.            ,ftell(outfp)
  823.            ,isNews
  824.            ,envelope.id
  825.            ,envelope.subject
  826.            ,envelope.from[0] ? envelope.from : "Annonymous"
  827.            ,group);
  828.  
  829.     /* Print Header */
  830.     fprintf(outfp, "<!-- Header -->\n<DL>\n");;
  831.     Item("Subject", envelope.subject);
  832.     Item("From", envelope.from);
  833.     if (envelope.address[0])
  834.         fprintf(outfp, "<DT>Reply to:<DD><ADDRESS>%s</ADDRESS>\n", envelope.address);
  835.     Item("Organization", envelope.organization);
  836.     Item("Date", envelope.date);
  837.     xItem(envelope.xrefs[0], "In-Reply-To", Anchor(envelope.xrefs));
  838.     xItem(envelope.refs[0], "References", Anchor(envelope.refs));
  839.     Item("Followup-To", envelope.followup);
  840.     Item("cc", envelope.cc);
  841.     Item("bcc", envelope.bcc);
  842.     Item("Keywords", envelope.keywords);
  843.     if (isNews)
  844.         fprintf(outfp, "<DT>Usenet %s:<DD>Article %d\n", group, isNews);
  845.     xItem((envelope.newsgroups[0]) && (!isNews || strcmp(group, envelope.newsgroups)),
  846.           "Cross Posted Newsgroups", envelope.newsgroups);
  847.     fprintf(outfp, "</DL>\n");
  848.     /* If we saw a MIME Header send out a warning message */
  849.     if (mime)
  850.         fprintf(outfp, "\
  851. <P><STRONG>WARNING: The message contained a MIME header (NOT YET Supported)</STRONG><P>\n");
  852. #undef Item
  853. #undef xItem
  854. }
  855.  
  856. int
  857. ParseMail(contents_t ** Contents, FILE * infp, FILE * outfp)
  858. {
  859.     char            tmp[BUFSIZ];
  860.     int             count = 0;
  861.     int             lines = 0;
  862.     int             isNews;
  863.     char           *group;
  864.  
  865.     while (fgets(tmp, sizeof(tmp), infp) != NULL) {
  866.         StripTail(tmp);
  867.         if ((isNews = IsMailFromLine(tmp)) == 0 || IsNewsLine(tmp, &isNews, &group) == 0) {
  868.             lines = 0;
  869.             /* Mail header */
  870.             if (count++)
  871.                 fprintf(outfp, "</PRE>\n");
  872.             LocateAnchors(Contents, isNews, group, infp, outfp);
  873.         } else {
  874.             if (lines++ == 0)
  875.                 fprintf(outfp, "<PRE>\n\n");
  876.             PutBodyLine(tmp, outfp);    /* Body */
  877.         }
  878.     }            /* while */
  879.     if (lines) fprintf(outfp, "</PRE>");
  880.     return count;
  881. }
  882.  
  883. static void
  884. Fatal(int code, const char *message)
  885. {
  886.     /* Send the error message to stdout */
  887.     printf("<TITLE>Mail Server Error Message</TITLE>\n\
  888. <PLAINTEXT>\n\n%s\n\n", message);
  889.     exit(code);
  890. }
  891.  
  892. static const char *
  893. basename(char *string)
  894. {
  895.     const char     *tcp;
  896.  
  897.     for (tcp = string + strlen(string); *tcp != '/' && tcp > string; tcp--)
  898.          /* loop */ ;
  899.     return (*tcp == '/') ? ++tcp : tcp;
  900. }
  901.  
  902. static int
  903. SendDocument(const char *title, const char *filename, long start, long end)
  904. {
  905.     long            length = end - start;
  906.     int             ch;
  907.     FILE           *fp;
  908.  
  909.     if ((fp = fopen(filename, "r")) == NULL)
  910.         return -1;
  911.  
  912.     if (fseek(fp, start, 0) == -1)
  913.         return -1;    /* seek error */
  914.  
  915.     /* Produce HTML Document */
  916.     fputs("<HTML>\n<HEAD>\n<TITLE>", stdout);
  917.     fputs(title, stdout);
  918.     fputs("</TITLE>\n</HEAD>\n<BODY>\n", stdout);
  919.     fputs("<!-- Message Body Follows -->\n", stdout);
  920.     while ((ch = getc(fp)) != EOF && length--)
  921.         putc((char) ch, stdout);
  922.     fputs("</BODY></HTML>\n", stdout);
  923.     return 0;
  924. }
  925.  
  926. int
  927. FetchMessage(const char *name)
  928. {
  929.     FILE           *fp;
  930.     char            tmp[BUFSIZ];
  931.     const char      hash = HASH(name);
  932.     int             result = -1;
  933.  
  934.     if ((fp = fopen(IndexFile, "r")) != NULL)
  935.         while (fgets(tmp, sizeof(tmp), fp) != NULL)
  936.             if (tmp[0] == hash) {
  937.                 char           *anchor;
  938.                 char           *filename;
  939.                 char           *title;
  940.                 char           *range;
  941.  
  942.                 anchor = strtok(tmp+1, "\t");
  943.                 filename = strtok(NULL, "\t");
  944.                 range = strtok(NULL, "\t");
  945.                 title = strtok(NULL, "\n");
  946.                 if (anchor && filename && range && strcmp(anchor, name) == 0) {
  947.                     long            start, end;
  948.  
  949.                     if (sscanf(range, "%ld-%ld", &start, &end) != 2) continue;
  950.                     result = SendDocument(
  951.                             title ? title : anchor,
  952.                             DecodeAnchor(tmp, filename),
  953.                             start, end
  954.                          );
  955.                     break;
  956.                 }
  957.             }
  958.     fclose(fp);
  959.     return result;
  960. }
  961.  
  962.  
  963. static FILE    *
  964. OpenContentsFile(const char *name, const char *mode)
  965. {
  966.     char            tmp[MAXPATHLEN];
  967.  
  968.     strcpy(tmp, name);
  969.     strcat(tmp, body_ext);
  970.     return fopen(tmp, mode);
  971. }
  972.  
  973. int
  974. main(int argc, char **argv)
  975. {
  976.     FILE           *outfp;
  977.     FILE           *fp;
  978.     contents_t     *Contents = NULL;
  979.     char            filename[MAXPATHLEN];
  980.  
  981.     InitCharTable();
  982.     if (argc == 2) {
  983.         DecodeAnchor(filename, argv[1]);
  984.         if (strncmp(filename, "/MessageID/", 11) == 0) {
  985.             if (FetchMessage(filename + 11))
  986.                 Fatal(E_ENOENT);
  987.             return 0;
  988.         } else {
  989.             char            tmp[MAXPATHLEN];
  990.             struct stat     statbuf1;
  991.             struct stat     statbuf2;
  992.  
  993.             /* Check if $1.html exists */
  994.             strcpy(tmp, filename);
  995.             strcat(tmp, DIRECTORY_EXTENSION);
  996.             if (stat(filename, &statbuf1) == 0 && stat(tmp, &statbuf2) == 0 &&
  997.                 statbuf2.st_mtime > statbuf1.st_mtime && statbuf2.st_size > 200) {
  998.                 /* The cache exists and its newer */
  999.                 if ((outfp = fopen(tmp, "r")) != NULL) {
  1000.                     CatStream(outfp, stdout);    /* Cat the cache */
  1001.                     fclose(outfp);
  1002.                     return 0;    /* DONE */
  1003.                 } else
  1004.                     outfp = stdout;    /* Don't bother with caching */
  1005.             } else if ((outfp = fopen(tmp, "w+")) == NULL)
  1006.                 outfp = stdout;
  1007.         }
  1008.     } else
  1009.         Fatal(E_USAGE);
  1010.  
  1011.     /* Open Input and parse to build Messages file */
  1012.     if ((fp = OpenContentsFile(filename, "w")) != NULL) {
  1013.         FILE           *infp;
  1014.  
  1015.         if ((infp = fopen(filename, "r")) == NULL)
  1016.             Fatal(E_ENOENT);
  1017.         ParseMail(&Contents, infp, fp);
  1018.         fclose(infp);
  1019.         fflush(fp);
  1020.         DumpDictionary(Contents, filename, ftell(fp));
  1021.         fclose(fp);
  1022.     } else
  1023.         Fatal(E_NOTEMP);
  1024.  
  1025.     /* Produce HTML Entry Document */
  1026.     fputs("<!-- This Document has been Machine Converted -->\n", outfp);
  1027.     fprintf(outfp, "<HTML>\n<HEAD>\n<TITLE>%s</TITLE>\n</HEAD>\n<BODY>\n", basename(filename));
  1028.     if (Contents)
  1029.         PrintContents(Contents, outfp);
  1030.     else        /* Was an error, so just cat the contents to recover */
  1031.         CatStream(OpenContentsFile(filename, "r"), outfp);
  1032.     fputs("</BODY></HTML>\n", outfp);
  1033.     if (fileno(outfp) != fileno(stdout))
  1034.         CatStream(outfp, stdout);    /* Cat File to stdout */
  1035.     return 0;
  1036. }
  1037.